Abschlussprojekt - Data Analytics - 2. EDA¶

Explorative Datenanalyse¶

In [1]:
# Import der Visualisierungsbibliothek und Pandas

import pandas as pd
import plotly_express as px
In [2]:
# Lies vorbereitete Daten aus Pickle-Datei ein

df = pd.read_pickle("kiva_data_after_processing.pkl")

Unvollständig finanzierte Projekte¶

Unser Businessmodell hängt davon ab, dass möglichst viele Projekte erfolgreich finanziert werden, damit weiterhin Darlehnsnehmende ihre Projekte einstellen und Investierende Geld zur Verfügung stellen.
Es kann nicht das Ziel sein, die Darlehnshöhe zu steigern, denn die Kredite müssen realistisch rückzahlbar sein.

Je höher die Finanzierungsrate eines Projekts, desto wahrscheinlicher ist es auch, dass die Investition erfolgreich ist und den geplanten Return on Investment abwirft.
Ich möchte daher zunächst die Finanzierungsraten genauer anschauen bzw diejenigen Projekte anschauen, die nicht vollständig finanziert wurden.

Datenselektion¶

Ich betrachte zunächst die Anzahl und den Anteil unvollständig finanzierter Projekte, also diejenigen Projekte mit funding_ratio < 100%:

In [3]:
# Label für Kategorien festlegen

category = ["0%", "1-99%", "100%"]

# Wertebereiche für Kategorien (bins) festlegen

funding_ratio = [-0.1, 0, 99,100]

# Teile die Projekte in die drei oben festegelegten Kategorien ein

df["funding"] = pd.cut(x=df.loc[:,"funding_ratio"],
                           bins=funding_ratio,
                           labels=category
                           )

# Überprüfe, wie die Werte auf die Kategorien verteilt sind

df_cats = df.funding.value_counts()
df_cats
Out[3]:
100%     598590
1-99%     44747
0%         3278
Name: funding, dtype: int64
In [4]:
# Lege Data Frame mit ausschließlich unvollständig finanzierten Projekten an

df_incomplete = df.loc[(df.loc[:,"funding_ratio"]<100),:]

# Ermittle Anzahl unvollständig finanzierter Projekte

number = df_incomplete.funding_ratio.value_counts().sum()

# Ermittle Anteil unvollständig finanzierter Projekte in Bezug auf die komplette Anzahl an Projekten

ratio = (df_incomplete.funding_ratio.value_counts().sum()/df.funding_ratio.value_counts().sum())*100
ratio = ratio.round(decimals=1)

# Gib Anzahl und Anteil aus

print(f"Anzahl unvollständig finanzierter Projekte: {number}")
print(f"Anteil unvollständig finanzierter Projekte: {ratio} %")
Anzahl unvollständig finanzierter Projekte: 48035
Anteil unvollständig finanzierter Projekte: 7.4 %

Im Folgenden betrachte ich nur die unvollständig finanzierten Projekte, die immerhin 7,4% aller Projekte ausmachen.

Visualisierung als Histogramm¶

Für die erste, univariate Visualisierung verwende ich ein Histogramm, um die Anzahl unvollständig finanzierter Projekte bezöglich unterschiedlicher Finanzierungsraten zu analysieren.

In [5]:
# Erstelle Histogramm

fig = px.histogram(df_incomplete,
                   x = "funding_ratio",
                   labels = {"funding_ratio": "Finanzierungsrate in %"},
                   nbins = 25,
                   title = "Title",
                   hover_data = df_incomplete.columns)

# Lege die Farbe für die Bins im Histogramm fest

color_sequence = ["#faaf72"] * 25  # Default für alle Bins
color_sequence[0] = "#921212"      # Hebe den ersten Bin hervor

# Update color_discrete_sequence Eigenschaft des Histogramms

fig.update_traces(marker=dict(color=color_sequence))

# Passe Layout der Grafik an

fig.update_layout(
                  width=1000,
                  height=620,
                  title={
                    'text': '<b>Anzahl unvollständig finanzierter Projekte nach Finanzierungsrate</b>',
                    'y':0.9,
                    'x':0.5,
                    'xanchor': 'center'
                    },
                  title_font_size=20,
                  yaxis_title="Anzahl",
                  bargap=0.1,
                  plot_bgcolor = "rgba(0, 0, 0, 0)",
                  paper_bgcolor = "rgba(0, 0, 0, 0)"
                  )

# Stelle sicher, dass die Bins bei 0 starten und bei 99 enden

fig.update_traces(xbins=dict(
                  start=0.0,
                  end=99.0
                  ))

# Zeige horizontale Linie für den Durchschnittswert (Anzahl unvollständiger Projekte/Anzahl bins = 48035/20 = 2402)

fig.add_hline(y=2402,
              line_width=3,
              line_dash="dash",
              line_color="grey"
              )

# Füge eine Fußnote zur Erklärung der Linie hinzu

fig.add_annotation(
                   showarrow=False,
                   text="Durchschnitt",
                   font=dict(size=10), 
                   x=99,
                   y=2500
                   )
# Plotte Graph

fig.show()

Interpretation¶

Aufbau des Plots¶

Das Histogramm zeigt auf der x-Achse die Finanzierungsrate in Prozent. Das entspricht dem erfolgreich finanzierten Anteil am gewünschten Gesamtdarlehn. Je höher der Prozentsatz, desto näher sind die Projektinitiator*innen an ihr Finanzierungsziel gekommen.
Auf der y-Achse ist die Anzahl der Projekte abgetragen.
Der Durchschnitt ist als Quotient von Gesamtanzahl und Anzahl der Balken im Histogramm eingezeichnet.

Erwartungsbild¶

Es wäre zu erwarten, dass sich die unvollständig finanzierten Projekte relativ gleichmäßig auf die Finanzierungsraten aufteilen.

Ist-Zustand¶

Besonders in den Randbereichen gibt es deutliche Abweichungen vom Erwartungsbild. Wir sehen einen großen Peak bei 0, dann einen starken Abfall in den kleinen Prozentbereichen. In der Mitte steigt die Kurve auf überdurchschnittliche Werte an um dann gegen 100% wieder stark abzufallen.

Vergleich Erwartungsbild und Ist-Zustand¶

Fast doppelt so viele Projekte wie erwartet konnten gar keine Finanzierung erzielen (<2,5% Finanzierungsrate für 4450 Projekte). Am anderen Ende der Skala gibt es dagegen unterdurschnittlich viele Projekte mit Finanzierungsraten zwischen ~83% und 100%.
Dafür wäre eine mögiche Erklärung, dass wenn ein Projekt schon über 80% seiner Zielsumme erreicht hat, es sehr wahrscheinlich ist, dass das Projekt Erfolg hat, selbst wenn nicht mehr 100% des angestrebten Betrags erreicht werden. Diese Projekte sind für Investoren also sher attraktiv.

Die nächste Fragestellung lautet also, warum es so eine große Anzahl (fast) komplett unfinanzierter Projekte gibt.
Dafür will ich zunächst herausfinden, ob es in der globalen Verteilung der unfinanzierten Projekte Auffälligkeiten oder Muster gibt.

Globale Verteilung von Projekten mit FInanzierungsrate 0%¶

Datenselektion¶

Zunächst aggregiere ich die unfinanzierten Projekte auf Länderebene. Neben den absoluten Zahlen ist auch der Prozentsatz je Land interessant.

In [6]:
# Lege Data Frame mit ausschließlich unfinanzierten Projekten an

df_zero = df.loc[(df.loc[:,"funding_ratio"]==0),:]

# Aggregiere unfinanzierte Projekte pro Land

df_zero_agg = df_zero.groupby(by="country", as_index=False).agg(func={"country_code":"count"})
In [7]:
# Aggregiere alle Projekte pro Land

df_agg = df.groupby(by="country", as_index=False).agg(func={"country_code":"count"})
In [8]:
# Kombiniere beide Dataframes, so dass beide Informationen je Land in einem df vorliegen

df_country = df_agg.merge(df_zero_agg, left_on="country", right_on="country", how='inner')

# Gib den Spalten aussagekräftigere Namen

df_country = df_country.rename(columns={"country_code_x": "projects_total", "country_code_y": "projects_unfunded"})

# Berechne Prozentsatz unfinanzierter Projekte

df_country["unfunded_projects_ratio"] = (df_country["projects_unfunded"] / df_country["projects_total"]) * 100
df_country["unfunded_projects_ratio"] = df_country.unfunded_projects_ratio.round(decimals=1)
df_country
Out[8]:
country projects_total projects_unfunded unfunded_projects_ratio
0 Afghanistan 2 0 0.0
1 Albania 1934 12 0.6
2 Armenia 8629 22 0.3
3 Azerbaijan 1942 0 0.0
4 Belize 124 0 0.0
... ... ... ... ...
82 Vietnam 9532 59 0.6
83 Virgin Islands 2 2 100.0
84 Yemen 2313 6 0.3
85 Zambia 775 0 0.0
86 Zimbabwe 3973 6 0.2

87 rows × 4 columns

Ich brauche noch eine Kennzahl, die die absolute und die relative Information verbindet.
Dafür dividiere ich die Anzahl der unfinanzierten Projekte durch die unfunded_projects_ratio und ziehe die Wurzel aus dem Ergebnis. Die Wurzel ziehe ich, damit die Spannweite der Werte kleiner wird.

In [9]:
# Berechne neue Kennzahl als "unfunded_projects_KPI"

df_country["unfunded_projects_KPI"] = (df_country["projects_unfunded"] * df_country["unfunded_projects_ratio"])**(1/2)
df_country["unfunded_projects_KPI"] = df_country["unfunded_projects_KPI"].astype("int64")
df_country.sort_values(by="unfunded_projects_KPI",ascending=False).head(10)
Out[9]:
country projects_total projects_unfunded unfunded_projects_ratio unfunded_projects_KPI
80 United States 6092 837 13.7 107
34 Kenya 73525 526 0.7 19
54 Pakistan 26453 242 0.9 14
83 Virgin Islands 2 2 100.0 14
22 El Salvador 39845 223 0.6 11
49 Myanmar (Burma) 1870 46 2.5 10
53 Nigeria 5696 82 1.4 10
15 Colombia 21961 156 0.7 10
11 Cambodia 26957 170 0.6 10
76 Togo 5747 71 1.2 9

Visualisierung als Choropleth-Karte¶

In [10]:
# Erstelle Choropleth Map

fig = px.choropleth(data_frame=df_country,
                    locations='country',
                    locationmode='country names',
                    color='unfunded_projects_KPI',
                    color_continuous_scale= "OrRd",
                    labels={"projects_unfunded": "Anzahl",
                            "unfunded_projects_ratio": "Anteil in %",
                            "unfunded_projects_KPI": "Kennzahl"},
                    hover_name="country",
                    hover_data={"country":False,"projects_unfunded":True, "unfunded_projects_ratio": True}
                    )

# Zeige Kontur um die Länder

fig.update_traces(marker_line_width=0.5)

# Update Layout

fig.update_layout(geo={"resolution":50,
                       "showframe":False,
                       "showcoastlines":False,
                       "projection_type":"equirectangular"
                       },
                  title={
                       'text': '<b>Unfinanzierte Projekte nach Ländern</b>',
                       'x':0.5,
                       'xanchor': 'center',
                       'yanchor': 'top'
                       },
                  title_font_size=20
                  )

# Plotte Graph

fig.show()

Interpretation¶

Aufbau des Plots¶

Für jedes Land, in dem es Crowd-Investment-Projekte gibt, zeigt die Farbe des Landes an, wie viele Projekte mit Finanzierungsrate 0% es in dem jeweiligen Land gibt, und zwar nicht als absolute Anzahl, sondern als Kombination von absoluten und relativem Anteil.
Je dunkler die Farbe, desto mehr unfinanzierte Projekte gibt es.

Erwartungsbild¶

Es wäre zu erwarten, dass sich die unfinanzierten Projekte relativ gleichmäßig auf alle Länder verteilen.

Ist-Zustand¶

Die USA sind als einziges Land dunkelrot markiert, die anderen Länder heben sich kaum von der beigen Farbe ab, die die Null markiert.

Vergleich Erwartungsbild und Ist-Zustand¶

Die USA stechen als einziges Land deutlich aus allen anderen hervor.
Daher möchte ich als nächtes untersuchen, was die unfinanzierten Projekte in den USA von den anderen Projekten unterscheidet.

Unfinanzierte vs. finanzierte Projekte in den USA vs. World¶

Datenselektion¶

Ich teile alle Projekte in vier Kategorien ein:

  • Projekte in den USA mit geringer Finanzierungsrate ("USA unfunded")
  • Projekte in den USA mit hoher Finanzierungsrate ("USA funded")
  • Projekte im Rest der Welt mit geringer Finanzierungsrate ("World unfunded")
  • Projekte im Rest der Welt mit hoher Finanzierungsrate ("World funded")

Als geringe Finanzierungsrate betrachte ich in diesem Zusammenhang Werte unter 20%, als hoch alles über 80%.

Für jede der Kategorien will ich die durchschnittliche angestrebte Darlehnssumme je Themenbereich aufzeigen.

In [11]:
# Erstelle neuen Dataframe mit neuer Spalte, die die Kategorisierung enthält

df_usa = df

df_usa.loc[(df_usa.loc[:,"country"]=="United States") & (df_usa.loc[:,"funding_ratio"]<20),"funding_cat"] = "USA unfunded"
df_usa.loc[(df_usa.loc[:,"country"]=="United States") & (df_usa.loc[:,"funding_ratio"]>80),"funding_cat"] = "USA funded"
df_usa.loc[(df_usa.loc[:,"country"]!="United States") & (df_usa.loc[:,"funding_ratio"]<20),"funding_cat"] = "World unfunded"
df_usa.loc[(df_usa.loc[:,"country"]!="United States") & (df_usa.loc[:,"funding_ratio"]>80),"funding_cat"] = "World funded"

df_usa
Out[11]:
funded_amount loan_amount activity sector country_code country currency term_in_months lender_count repayment_interval borrowers_female borrowers_male borrower_count funding_ratio avg_investment funding funding_cat
0 300 300 Fruits & Vegetables Food PK Pakistan PKR 12 12 irregular 1 0 1 100.0 25.00000 100% World funded
1 575 575 Rickshaw Transportation PK Pakistan PKR 11 14 irregular 2 0 2 100.0 41.07143 100% World funded
2 150 150 Transportation Transportation IN India INR 43 6 bullet 1 0 1 100.0 25.00000 100% World funded
3 200 200 Embroidery Arts PK Pakistan PKR 11 8 irregular 1 0 1 100.0 25.00000 100% World funded
4 400 400 Milk Sales Food PK Pakistan PKR 14 16 monthly 1 0 1 100.0 25.00000 100% World funded
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
671179 0 25 Livestock Agriculture PY Paraguay USD 13 0 monthly 1 0 1 0.0 0.00000 0% World unfunded
671181 0 25 Livestock Agriculture PK Pakistan PKR 13 0 monthly 1 0 1 0.0 0.00000 0% World unfunded
671182 0 125 Livestock Agriculture MX Mexico MXN 13 0 monthly 2 0 2 0.0 0.00000 0% World unfunded
671184 0 875 Livestock Agriculture BO Bolivia BOB 13 0 monthly 2 0 2 0.0 0.00000 0% World unfunded
671188 0 250 Livestock Agriculture GH Ghana GHS 13 0 monthly 1 0 1 0.0 0.00000 0% World unfunded

646617 rows × 17 columns

In [12]:
# Erstelle Dataframe, wo die Anzahl der Projekte je Finanzierungskategorie und Themenbereich aggregiert wird

df_usa_agg = df_usa.groupby(by=["funding_cat","sector"], as_index=False).agg(func={"loan_amount":"mean"})
df_usa_agg
Out[12]:
funding_cat sector loan_amount
0 USA funded Agriculture 7437.425595
1 USA funded Arts 5081.658291
2 USA funded Clothing 4821.958175
3 USA funded Construction 4992.796610
4 USA funded Education 5267.796610
5 USA funded Entertainment 4335.000000
6 USA funded Food 6184.717608
7 USA funded Health 5255.357143
8 USA funded Housing 5791.000000
9 USA funded Manufacturing 5016.379310
10 USA funded Personal Use 6236.111111
11 USA funded Retail 4772.098214
12 USA funded Services 4812.901182
13 USA funded Transportation 4861.111111
14 USA funded Wholesale 4760.937500
15 USA unfunded Agriculture 7126.442308
16 USA unfunded Arts 3510.156250
17 USA unfunded Clothing 4120.905172
18 USA unfunded Construction 3848.437500
19 USA unfunded Education 3882.843137
20 USA unfunded Entertainment 4043.072289
21 USA unfunded Food 5090.959821
22 USA unfunded Health 4571.428571
23 USA unfunded Housing 3323.214286
24 USA unfunded Manufacturing 6166.666667
25 USA unfunded Personal Use 6825.000000
26 USA unfunded Retail 4453.808594
27 USA unfunded Services 4115.503247
28 USA unfunded Transportation 3559.210526
29 World funded Agriculture 765.534036
30 World funded Arts 923.287286
31 World funded Clothing 1076.003810
32 World funded Construction 989.468570
33 World funded Education 975.069963
34 World funded Entertainment 939.162754
35 World funded Food 827.496484
36 World funded Health 995.570395
37 World funded Housing 690.034044
38 World funded Manufacturing 843.266721
39 World funded Personal Use 486.760125
40 World funded Retail 734.362057
41 World funded Services 876.714104
42 World funded Transportation 631.519291
43 World funded Wholesale 1360.838870
44 World unfunded Agriculture 1125.655022
45 World unfunded Arts 1037.083333
46 World unfunded Clothing 1265.844298
47 World unfunded Construction 1671.052632
48 World unfunded Education 1736.466165
49 World unfunded Entertainment 1581.250000
50 World unfunded Food 1137.866242
51 World unfunded Health 1612.656250
52 World unfunded Housing 1096.893204
53 World unfunded Manufacturing 1493.750000
54 World unfunded Personal Use 855.503731
55 World unfunded Retail 1058.257987
56 World unfunded Services 1080.022659
57 World unfunded Transportation 1311.228070
58 World unfunded Wholesale 26162.500000

Visualisierung als Bubble Chart¶

In [15]:
# Erstelle Scatterplot

fig = px.scatter(df_usa_agg,
                 x=["funding_cat"],
                 y='sector',
                 size='loan_amount',
                 color='loan_amount',
                 color_continuous_scale= "OrRd",
                 labels={"sector": "Sektor", "loan_amount": "\u2300 Darlehnssumme", "value": ""},
                 hover_name="sector"
                 )

# Lege Farbe für Extremwert fest, damit die anderen Werte auf der Farbskala nicht untergehen

excluded_index = 58        # Index des Extremwerts (siehe df_usa_agg: World unfunded, Wholesale)
excluded_color_value = 0   # Weise dem Extremwert einen Farbwert zu (d.h., die Farbe, die dem Wert 0 entspricht)

# Update Farbe für Extremwert

fig.data[0].marker.color[excluded_index] = excluded_color_value

# Zeige Umriss für die Bubbles

fig.update_traces(marker=dict(line=dict(width=0.5, color='DarkSlateGrey')),
                  selector=dict(mode='markers')
                  )                  
                  
# Update Graph Layout
    
fig.update_layout(width=700,
                  title={'text': '<b>Finanzierte vs. unfinanzierte Projekte in den USA vs. World</b>',
                    'x':0.5,
                    'xanchor': 'center',
                    'yanchor': 'top',
                    },
                  title_font_size = 20,
                  plot_bgcolor = "rgba(0, 0, 0, 0)"
                  )

# Update Grid, damit es sichtbar ist

fig.update_yaxes(showline=True, linecolor='white', gridwidth=0.2, gridcolor='LightGrey')

# Plot anzeigen

fig.show()

Interpretation¶

Aufbau des Plots¶

Für jede Kategorie auf der x-Achse und jeden Themenbereich auf der y-Achse zeigt die Farbe und die Größe des Bubbles an, wie hoch die durchschnittliche Darlehnssumme ist.
Je dunkler die Farbe und größer die Blase, desto höher ist die durchschnittliche angezielte Darlehnssumme.

Erwartungsbild¶

Es wäre zu erwarten, dass sich die unfinanzierten Projekte von den finanzierten Projekte abheben.

Ist-Zustand¶

Die Unterschiede zwischen den USA und dem Rest der Länder sind erheblich, in den USA ist die durchschnittliche Darlehnssumme über alle Sektoren deutlich größer als bei den anderen Ländern (von einem Extremwert im Bereich "Wholesale" abgesehen).

Vergleich Erwartungsbild und Ist-Zustand¶

Die Bubbles der unfinanzierten Projekte im Rest der Welt sind über alle Sektoren hinweg etwas größer als die der finanzierten Projekte.
Zwischen den unfinanzierten und den finanzierten Projekten innerhalb der USA gibt es diesen Unterschied allerdings nicht. Im Gegenteil, dort sind die Bubbles der unfinanzierten Projekte tendentiell kleiner als die der finanzierten.

Zusammenfassung¶

Die Ausgangsfrage, was die unfinanzierten Projekte von den finanzierten Projekten abhebt, konnte bisher nicht abschließend geklärt werden. Klar ist jedoch, dass sich diese Projekte überwiegend und überproportional auf die USA konzentrieren.

Die USA sind die einzige vertretene Industrienation.
Ich halte es aus den folgenden Gründen für möglich, dass die Mehrheit der Investierenden es vorzieht, in einem Entwicklungs- oder Schwellenland zu investieren: Erstens erscheinen dort die Lebensumstände insgesamt schwieriger und Investoren wollen einen Beitrag leisten, die Situation für Kleinunternehmer zu verbessern. Zweitens sind diese Staaten nicht in der Lage, ihre Bevölkerung zu befähigen, für ihren Lebensunterhalt zu sorgen. Drittens können auf Grund der geringeren Lebenshaltungskosten und der oft schwachen Währungen schon relativ geringe Dollarbeträge viel bewegen.

Im nächsten Schritt würde ich versuchen, mehr Daten über die Investierenden zu erhalten, zum Beispiel aus welchen Ländern Geld in welche Länder fließt.